/* * Copyright 2013 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.apps.dashclock.render; import com.google.android.apps.dashclock.ExtensionManager; import com.google.android.apps.dashclock.LogUtils; import com.google.android.apps.dashclock.Utils; import com.google.android.apps.dashclock.WidgetClickProxyActivity; import com.google.android.apps.dashclock.configuration.AppChooserPreference; import com.google.android.apps.dashclock.configuration.AppearanceConfig; import com.google.android.apps.dashclock.configuration.ConfigurationActivity; import net.nurik.roman.dashclock.R; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import java.util.List; import java.util.Locale; import static com.google.android.apps.dashclock.ExtensionManager.ExtensionWithData; /** * Abstract helper class in charge of core rendering logic for DashClock widgets. */ public abstract class DashClockRenderer { private static final String TAG = LogUtils.makeLogTag(DashClockRenderer.class); private static final int MAX_COLLAPSED_EXTENSIONS = 3; public static final String PREF_CLOCK_SHORTCUT = "pref_clock_shortcut"; public static final String PREF_HIDE_SETTINGS = "pref_hide_settings"; protected Context mContext; protected Options mOptions; protected ExtensionManager mExtensionManager; protected DashClockRenderer(Context context) { mContext = context; mExtensionManager = ExtensionManager.getInstance(context); } public void setOptions(Options options) { mOptions = options; } public Object renderWidget(Object container) { ViewBuilder vb = onCreateViewBuilder(); Resources res = mContext.getResources(); // Load data from extensions List<ExtensionWithData> extensions = mExtensionManager.getActiveExtensionsWithData(); int activeExtensions = extensions.size(); int visibleExtensions = 0; for (ExtensionWithData ewd : extensions) { if (!ewd.latestData.visible()) { continue; } ++visibleExtensions; } // Load some settings SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); boolean hideSettings = sp.getBoolean(PREF_HIDE_SETTINGS, false); // Determine if we're on a tablet or not (lock screen widgets can't be collapsed on // tablets). boolean isTablet = res.getConfiguration().smallestScreenWidthDp >= 600; // Pull high-level user-defined appearance options. int shadeColor = AppearanceConfig.getHomescreenBackgroundColor(mContext); boolean aggressiveCentering = AppearanceConfig.isAggressiveCenteringEnabled(mContext); boolean isExpanded = (mOptions.minHeightDp >= res.getDimensionPixelSize(R.dimen.min_expanded_height) / res.getDisplayMetrics().density); // Step 1. Load the root layout vb.loadRootLayout(container, isExpanded ? (aggressiveCentering ? R.layout.widget_main_expanded_forced_center : R.layout.widget_main_expanded) : (aggressiveCentering ? R.layout.widget_main_collapsed_forced_center : R.layout.widget_main_collapsed)); // Step 2. Configure the shade, if it should exist vb.setViewBackgroundColor(R.id.shade, shadeColor); vb.setViewVisibility(R.id.shade, (mOptions.target != Options.TARGET_HOME_SCREEN || shadeColor == 0) ? View.GONE : View.VISIBLE); // Step 3. Draw the basic clock face renderClockFace(vb); // Step 4. Align the clock face and settings button (if shown) boolean isPortrait = res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; if (aggressiveCentering) { // Forced/aggressive centering rules vb.setViewVisibility(R.id.settings_button_center_displacement, hideSettings ? View.GONE : View.VISIBLE); vb.setViewPadding(R.id.clock_row, 0, 0, 0, 0); vb.setLinearLayoutGravity(R.id.clock_target, Gravity.CENTER_HORIZONTAL); } else { // Normal centering rules boolean forceCentered = isTablet && isPortrait && mOptions.target != Options.TARGET_HOME_SCREEN; int clockInnerGravity = Gravity.CENTER_HORIZONTAL; if (activeExtensions > 0 && !forceCentered) { // Extensions are visible, don't center clock if (mOptions.target == Options.TARGET_LOCK_SCREEN) { // lock screen doesn't look at expanded state; the UI should // not jitter across expanded/collapsed states for lock screen clockInnerGravity = isTablet ? Gravity.LEFT : Gravity.RIGHT; } else { // home screen clockInnerGravity = (isExpanded && isTablet) ? Gravity.LEFT : Gravity.RIGHT; } } vb.setLinearLayoutGravity(R.id.clock_target, clockInnerGravity); boolean clockCentered = activeExtensions == 0 || forceCentered; // left otherwise vb.setLinearLayoutGravity(R.id.clock_row, clockCentered ? Gravity.CENTER_HORIZONTAL : Gravity.LEFT); vb.setViewVisibility(R.id.settings_button_center_displacement, hideSettings ? View.GONE : (clockCentered ? View.INVISIBLE : View.GONE)); int clockLeftMargin = res.getDimensionPixelSize(R.dimen.clock_left_margin); vb.setViewPadding(R.id.clock_row, clockCentered ? 0 : clockLeftMargin, 0, 0, 0); } // Settings button if (isExpanded) { vb.setViewVisibility(R.id.settings_button, hideSettings ? View.GONE : View.VISIBLE); vb.setViewClickIntent(R.id.settings_button, new Intent(mContext, ConfigurationActivity.class) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)); } // Step 6. Render the extensions (collapsed or expanded) vb.setViewVisibility(R.id.widget_divider, (visibleExtensions > 0) ? View.VISIBLE : View.GONE); if (isExpanded) { // Expanded style final Intent onClickTemplateIntent = WidgetClickProxyActivity.getTemplate(mContext); builderSetExpandedExtensionsAdapter(vb, R.id.expanded_extensions, onClickTemplateIntent); } else { // Collapsed style vb.setViewVisibility(R.id.collapsed_extensions_container, activeExtensions > 0 ? View.VISIBLE : View.GONE); boolean ellipsisVisible = false; vb.removeAllViews(R.id.collapsed_extensions_container); int slotIndex = 0; for (ExtensionWithData ewd : extensions) { if (!ewd.latestData.visible()) { continue; } if (slotIndex >= MAX_COLLAPSED_EXTENSIONS) { ellipsisVisible = true; break; } vb.addView(R.id.collapsed_extensions_container, renderCollapsedExtension(null, ewd)); ++slotIndex; } if (ellipsisVisible) { vb.addView(R.id.collapsed_extensions_container, vb.inflateChildLayout( R.layout.widget_include_collapsed_ellipsis, R.id.collapsed_extensions_container)); } } return vb.getRoot(); } public void renderClockFace(ViewBuilder vb) { vb.removeAllViews(R.id.time_container); vb.addView(R.id.time_container, vb.inflateChildLayout( AppearanceConfig.getCurrentTimeLayout(mContext), R.id.time_container)); vb.removeAllViews(R.id.date_container); vb.addView(R.id.date_container, vb.inflateChildLayout( AppearanceConfig.getCurrentDateLayout(mContext), R.id.date_container)); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); Intent clockIntent = AppChooserPreference.getIntentValue( sp.getString(PREF_CLOCK_SHORTCUT, null), Utils.getDefaultClockIntent(mContext)); if (clockIntent != null) { vb.setViewClickIntent(R.id.clock_target, clockIntent); } } public Object renderCollapsedExtension(Object container, ExtensionWithData ewd) { ViewBuilder vb = onCreateViewBuilder(); vb.loadRootLayout(container, R.layout.widget_include_collapsed_extension); Resources res = mContext.getResources(); int extensionCollapsedTextSizeSingleLine = res .getDimensionPixelSize(R.dimen.extension_collapsed_text_size_single_line); int extensionCollapsedTextSizeTwoLine = res .getDimensionPixelSize(R.dimen.extension_collapsed_text_size_two_line); String status = ewd.latestData.status(); if (TextUtils.isEmpty(status)) { status = ""; } if (status.indexOf("\n") > 0) { vb.setTextViewSingleLine(R.id.collapsed_extension_text, false); vb.setTextViewMaxLines(R.id.collapsed_extension_text, 2); vb.setTextViewTextSize(R.id.collapsed_extension_text, TypedValue.COMPLEX_UNIT_PX, extensionCollapsedTextSizeTwoLine); } else { vb.setTextViewSingleLine(R.id.collapsed_extension_text, true); vb.setTextViewMaxLines(R.id.collapsed_extension_text, 1); vb.setTextViewTextSize(R.id.collapsed_extension_text, TypedValue.COMPLEX_UNIT_PX, extensionCollapsedTextSizeSingleLine); } vb.setTextViewText(R.id.collapsed_extension_text, status.toUpperCase(Locale.getDefault())); StringBuilder statusContentDescription = new StringBuilder(); String expandedTitle = Utils.expandedTitleOrStatus(ewd.latestData); if (!TextUtils.isEmpty(expandedTitle)) { statusContentDescription.append(expandedTitle); } String expandedBody = ewd.latestData.expandedBody(); if (!TextUtils.isEmpty(expandedBody)) { statusContentDescription.append(" ").append(expandedBody); } vb.setViewContentDescription(R.id.collapsed_extension_text, statusContentDescription.toString()); vb.setImageViewBitmap(R.id.collapsed_extension_icon, Utils.loadExtensionIcon(mContext, ewd.listing.componentName, ewd.latestData.icon())); vb.setViewContentDescription(R.id.collapsed_extension_icon, ewd.listing.title); Intent clickIntent = ewd.latestData.clickIntent(); if (clickIntent != null) { vb.setViewClickIntent(R.id.collapsed_extension_target, WidgetClickProxyActivity.wrap(mContext, clickIntent, ewd.listing.componentName)); } return vb.getRoot(); } public Object renderExpandedExtension(Object container, Object convertRoot, ExtensionWithData ewd) { ViewBuilder vb = onCreateViewBuilder(); if (convertRoot != null) { vb.useRoot(convertRoot); } else { vb.loadRootLayout(container, R.layout.widget_list_item_expanded_extension); } if (ewd == null || ewd.latestData == null) { vb.setTextViewText(R.id.text1, mContext.getResources() .getText(R.string.status_none)); vb.setViewVisibility(R.id.text2, View.GONE); return vb.getRoot(); } vb.setTextViewText(R.id.text1, Utils.expandedTitleOrStatus(ewd.latestData)); String expandedBody = ewd.latestData.expandedBody(); vb.setViewVisibility(R.id.text2, TextUtils.isEmpty(expandedBody) ? View.GONE : View.VISIBLE); vb.setTextViewText(R.id.text2, ewd.latestData.expandedBody()); vb.setImageViewBitmap(R.id.icon, Utils.loadExtensionIcon(mContext, ewd.listing.componentName, ewd.latestData.icon())); vb.setViewContentDescription(R.id.icon, ewd.listing.title); Intent clickIntent = ewd.latestData.clickIntent(); if (clickIntent != null) { vb.setViewClickFillInIntent(R.id.list_item, WidgetClickProxyActivity.getFillIntent(clickIntent, ewd.listing.componentName)); } return vb.getRoot(); } protected abstract ViewBuilder onCreateViewBuilder(); protected abstract void builderSetExpandedExtensionsAdapter(ViewBuilder builder, int viewId, Intent onClickTemplateIntent); public static class Options { public static final int TARGET_HOME_SCREEN = 0; public static final int TARGET_LOCK_SCREEN = 1; public static final int TARGET_DAYDREAM = 2; public int target; public int minHeightDp; // Only used by WidgetRenderer public int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // optional // Only used by SimpleRenderer public boolean newTaskOnClick; public OnClickListener onClickListener; public Intent clickIntentTemplate; } public static interface OnClickListener { void onClick(); } }